// "Copyright [2021] <Copyright Shengkai Lin>"

#include "optical_transceiver.h"

namespace ai_sim {

Define_Module(OpticalTransceiver);

void OpticalTransceiver::initialize() {
    detectMode = false;
    my_index = par("my_index");
    my_global_index = par("my_global_index");
    msgSize = par("msgSize");
    safety_interval = par("safety_interval");
    detect_period = par("detect_period");
    out_channel = gate("out_port$o")->getTransmissionChannel();

    selfMsg_detect = new SimpleMsg;
    selfMsg_detect->setArgument(my_global_index);
    selfMsg_detect->setKind(DETECT_MSG);
    selfMsg_detect->setByteLength(msgSize);

    selfMsg_send = new SimpleMsg;
    selfMsg_send->setKind(SELF_MSG);
}

void OpticalTransceiver::sendDetectMsg() {
    if (!out_channel->isBusy()) {
        EV << "Send out a detect msg at nic" << endl;
        send(selfMsg_detect->dup(), "out_port$o");
    }
    // Disable send detect msg periodly
    // cancelEvent(selfMsg_detect);
    // scheduleAt(omnetpp::simTime() + detect_period, selfMsg_detect);
}

void OpticalTransceiver::replyAck(omnetpp::cMessage* msg) {
    msg->setKind(ACK_MSG);
    if (!out_channel->isBusy()) {
        send(msg, "out_port$o");
    } else {
        delete msg;
    }
    // If the out channel is busy means that the channel is ok
}

void OpticalTransceiver::sendMsginQueue() {
    if (!msg_queue.empty() &&
        (omnetpp::simTime() <= last_ack_time + safety_interval) &&
        !out_channel->isBusy()) {
        send(msg_queue.front(), "out_port$o");
        msg_queue.pop();
        cancelEvent(selfMsg_send);
        scheduleAt(out_channel->getTransmissionFinishTime(), selfMsg_send);
    } else if (!msg_queue.empty() &&
        !out_channel->isBusy()) {
        sendDetectMsg();
    }
}

void OpticalTransceiver::handleReceivingMsg(omnetpp::cMessage *msg) {
    last_ack_time = omnetpp::simTime();
    // refresh the detect time.
    // cancelEvent(selfMsg_detect);
    // scheduleAt(omnetpp::simTime() + detect_period, selfMsg_detect);
    // Try to send a msg
    sendMsginQueue();
    if (msg->getKind() == DETECT_MSG) {
        // If receive a detect msg
        EV << "Receive a detect msg at transceiver" << endl;
        replyAck(msg);
    } else if (msg->getKind() == ACK_MSG) {
        delete msg;
        EV << "Receive a ACK msg at transceiver" << endl;
    } else {
        // Else if the msg is just a normal data msg
        send(msg, "nic_port$o");
    }
}

void OpticalTransceiver::handleSendingMsg(omnetpp::cMessage *msg) {
    if ((omnetpp::simTime() <= last_ack_time + safety_interval)
        && !out_channel->isBusy()) {
        send(msg_queue.front(), "out_port$o");
        msg_queue.pop();
    } else if (!out_channel->isBusy()) {
        sendDetectMsg();
    }
}

void OpticalTransceiver::handleMessage(omnetpp::cMessage *msg) {
    omnetpp::cGate* inPort = msg->getArrivalGate();
    if (inPort == gate("out_port$i")) {
        // Receiving a msg
        handleReceivingMsg(msg);
    } else if (inPort == gate("nic_port$i")) {
        // Sending a msg
        msg_queue.push(msg);
        sendMsginQueue();
    } else if (msg->isSelfMessage()) {
        if (msg->getKind() == SELF_MSG) {
            sendMsginQueue();
        } else if (msg->getKind() == DETECT_MSG) {
            sendDetectMsg();
        }
    }
}

void OpticalTransceiver::finish() {
    cancelAndDelete(selfMsg_send);
    cancelAndDelete(selfMsg_detect);
}
}  // namespace ai_sim
